#ifndef __S5MESSAGE__
#define __S5MESSAGE__

/**
* Copyright (C), 2014-2015.
* @file
* s5 message definition.
*
* This file includes all s5 message data structures and interfaces, which are used by S5 modules.
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h>
#include <stdarg.h>
#include "basetype.h"
#include <type_traits>

#define MAX_NAME_LEN 96	///< max length of name used in s5 modules.
#define	S5MESSAGE_MAGIC		0x3553424e	///< magic number for s5 message.
#define _WRITE_OP_ 0x01
#define _READ_OP_ 0x02
#define _NODATA_OP_ 0
#define IS_WRITE_OP(op) ((op & 0x03) == _WRITE_OP_)
#define IS_READ_OP(op) ((op & 0x03) == _READ_OP_)

#define _OP_CODE_(x, dir) ((x<<2)|dir)
enum PfOpCode : uint8_t {
	S5_OP_WRITE = _OP_CODE_(0, _WRITE_OP_),
	S5_OP_READ  = _OP_CODE_(0, _READ_OP_),
	S5_OP_HEARTBEAT = _OP_CODE_(1, _NODATA_OP_),
	S5_OP_REPLICATE_WRITE = _OP_CODE_(1, _WRITE_OP_),
	S5_OP_COW_READ = _OP_CODE_(2, _READ_OP_),
	S5_OP_COW_WRITE = _OP_CODE_(2, _WRITE_OP_),
	S5_OP_RECOVERY_READ = _OP_CODE_(3, _READ_OP_),
	S5_OP_RECOVERY_WRITE = _OP_CODE_(3, _WRITE_OP_),
	S5_OP_RPC_ALLOC_BLOCK = _OP_CODE_(4, _NODATA_OP_),
	S5_OP_RPC_DELETE_BLOCK = _OP_CODE_(5, _NODATA_OP_),
};

const char* PfOpCode2Str(PfOpCode op);

enum PfMessageStatus : uint16_t{
	MSG_STATUS_SUCCESS = 0x0,
	MSG_STATUS_INVALID_OPCODE = 0x1,
	MSG_STATUS_INVALID_FIELD = 0x2,
	MSG_STATUS_CMDID_CONFLICT = 0x3,
	MSG_STATUS_DATA_XFER_ERROR = 0x4,
	MSG_STATUS_POWER_LOSS = 0x5,
	MSG_STATUS_INTERNAL = 0x6,
	MSG_STATUS_ABORT_REQ = 0x7,
	MSG_STATUS_INVALID_IO_TIMEOUT = 0x8,
	MSG_STATUS_INVALID_STATE = 0x09,
	MSG_STATUS_LBA_RANGE = 0x80,
	MSG_STATUS_NS_NOT_READY = 0x82,
	MSG_STATUS_NOT_PRIMARY = 0xC0,
	MSG_STATUS_NOSPACE = 0xC1,
	MSG_STATUS_READONLY = 0xC2,
	MSG_STATUS_CONN_LOST = 0xC3,
	MSG_STATUS_AIOERROR = 0xC4,
	MSG_STATUS_ERROR_HANDLED = 0xC5,
	MSG_STATUS_ERROR_UNRECOVERABLE = 0xC6,
	MSG_STATUS_AIO_TIMEOUT = 0xC7,
	MSG_STATUS_REPLICATING_TIMEOUT = 0xC8,
	MSG_STATUS_NODE_LOST = 0xC9,
	MSG_STATUS_LOGFAILED = 0xCA,
	MSG_STATUS_METRO_REPLICATING_FAILED = 0xCB,
	MSG_STATUS_RECOVERY_FAILED = 0xCC,
	MSG_STATUS_SSD_ERROR = 0xCD,
	MSG_STATUS_REP_TO_PRIMARY = 0xCE, //replicating write to primary node
	MSG_STATUS_NO_RESOURCE = 0xCF, //no memory or other resource

	MSG_STATUS_DEGRADE = 0x2000,
	MSG_STATUS_REOPEN = 0x4000,
};
inline PfMessageStatus operator | (PfMessageStatus lhs, PfMessageStatus rhs)
{
	typedef std::underlying_type <PfMessageStatus>::type T;
	return static_cast<PfMessageStatus>(static_cast<T >(lhs) | static_cast<T >(rhs));
}

/**
 * Get the name of s5message's status, get the string name refer to enum type.
 *
 * @param[in] 	msg_status	status enum type.
 * @return 	status's name on success, UNKNOWN_STATUS on failure.
 * @retval	UNKNOWN_STATUS				msg_status is invalid.
 */
const char* PfMessageStatus2Str(PfMessageStatus s);

#pragma pack(1)

/**
 *   s5message's head data structure definition.
 */
struct PfMessageHead
{
	PfOpCode /*uint8_t*/      opcode;
	uint8_t                   rsv1;
	uint16_t                  command_id;
	uint32_t                  snap_seq;
	uint64_t                  vol_id;
	uint64_t                  buf_addr;
	uint32_t                  rkey;
	uint16_t                  meta_ver;
	uint16_t                  rsv2;
	uint64_t                  offset;
	uint32_t                  length;
	uint32_t                  command_seq;  //use this field to hold io task sequence number
	uint64_t                  rsv3;
	uint64_t                  rsv4;
};
#define PF_MSG_HEAD_SIZE 64
static_assert(sizeof(PfMessageHead) == PF_MSG_HEAD_SIZE, "PfMessageHead");
#define SNAP_SEQ_HEAD 0xffffffff

struct PfMessageReply {
	PfMessageStatus  status;         /* did the command fail, and if so, why? */
	uint16_t  meta_ver;
	uint16_t  command_id;     /* of the command which completed */
	uint16_t  rsv0;
	uint32_t  command_seq;
	uint32_t  block_id; //for block allocation rpc, return new block id
	uint64_t  rsv2;
	uint64_t  rsv3;
};
#define PF_MSG_REPLY_SIZE 32
static_assert(sizeof(PfMessageReply) == PF_MSG_REPLY_SIZE, "PfMessageReply");

struct PfHandshakeMessage {
	int16_t io_timeout;
	union {
		int16_t qid;
		int16_t crqsize; //server return this on accept's private data, indicates real IO depth
		int16_t hsqsize;//host send queue size, i.e. max IO queue depth for ULP
	};
	int16_t rsv0;//host receive queue size
	int16_t rsv1;//host send queue size, i.e. max IO queue depth for ULP
	uint64_t vol_id; //srv1 defined by NVMe over Fabric
	int32_t rsv2;
	union {
		int16_t protocol_ver; //on client to server, this is protocol version
		int16_t hs_result; //on server to client, this is  shake result, 0 for success, others for failure.
	};
	uint16_t rsv3;
	uint64_t rsv4;
};
static_assert(sizeof(struct PfHandshakeMessage) == 32, "PfHandshakeMessage");
#pragma pack()

// a replica id is composed as:
// bit[63..24]  volume index, generated by conductor
// bit[23..4] shard index
// bit[3..0] replica index
#define VOL_ID_TO_VOL_INDEX(x)((x)>>24)
#define VOL_ID_TO_SHARD_INDEX(x)(((x)>>4) & 0x0fffff)
#define OFFSET_TO_SHARD_INDEX(offset) ((offset) >> (LBA_LENGTH_ORDER + SHARD_LBA_CNT_ORDER))
#define debug_data_len 10	///< debug data length.

/**
 * Dump data refer to debug_data_len.
 *
 * @param[in] data	 target data to dump.
 * @return 	pointer to string of debug_data_len's data.
 */
#define get_debug_data(data) ({                 \
	char buf[debug_data_len+1];               \
	do{                                       \
	memset(buf,0,debug_data_len+1);         \
	memcpy(buf,data,debug_data_len);        \
	}while(0);                                \
	buf;                                      \
})


#endif	/*__S5MESSAGE__*/


